/***********************************************************************************
 * 文件名： mcu_adc.c
 * 作者： 刘言
 * 版本： 5
 * 说明：
 * 		ADC驱动。
 * 修改记录：
 * 	2020/8/19: 初版。 刘言。
 *  2020/11/7: V2。新增扫描转换功能。单次转换现在不用在意ADC中断状态、是否单次模式，已经内部处理。
 *  2020/12/21: V3。mIsBusy增加volatile修饰，解决编译器优化后不再判断mIsBusy导致死机问题。
 *              跨线程（中断）全局变量同步务必加volatile。
 *  2021/3/20: V4.增加温度传感器开关静态配置项；初始化后ADC默认是否使能；异步转换功能；ADC使能、失能API。
 * 2022/07/15: 支持所有通道扫描；固定使用TIM2定时扫描；内部处理中断。
***********************************************************************************/
#include "HC32_LyLib.h"	
#include "mcu_adc.h"

#define GET_BYTE_1_BIT_NUMBER(b) ((((b)&0x01)==0?0:1) + (((b)&0x02)==0?0:1) + (((b)&0x04)==0?0:1) + (((b)&0x08)==0?0:1) + (((b)&0x10)==0?0:1) + (((b)&0x20)==0?0:1) + (((b)&0x40)==0?0:1) + (((b)&0x80)==0?0:1))

#ifdef ADC_USE_SCAN
#define ADC_HW_SCAN_CNT     GET_BYTE_1_BIT_NUMBER(ADC_SCAN_CHEN_MASK&0x00FF)
#else
#define ADC_HW_SCAN_CNT     0
#define ADC_SCAN_CHEN_MASK  0
#endif

#if (defined ADC_USE_SCAN || defined ADC_USE_ASYNC)
static volatile bool mIsBusy = false;       // 标记正在转换中（单次转换和扫描转换不能同时进行）
#endif
#if (defined ADC_USE_ASYNC)
static adc_result_callback_t mResultCall = NULL;
#endif
#ifdef ADC_USE_SCAN
static volatile bool mScanning = false;
#endif

void ADC_IRQHandler()
{
#if (HC32_MCU_SERIES == HC32F005)
#ifdef ADC_USE_SCAN
	if(M0P_ADC->IFR_f.CONT_INTF)	// 硬件扫描（连续）转换完成
	{
        _Adc_ScanCallBackCh0(M0P_ADC->RESULT0);
        _Adc_ScanCallBackCh1(M0P_ADC->RESULT1);
        _Adc_ScanCallBackCh2(M0P_ADC->RESULT2);
        _Adc_ScanCallBackCh3(M0P_ADC->RESULT3);
        _Adc_ScanCallBackCh4(M0P_ADC->RESULT4);
        _Adc_ScanCallBackCh5(M0P_ADC->RESULT5);
        _Adc_ScanCallBackCh6(M0P_ADC->RESULT6);
        _Adc_ScanCallBackCh7(M0P_ADC->RESULT7);
        M0P_ADC->CR1_f.CT = 0;      // 切换到单次转换模式
    #if (ADC_SCAN_CHEN_MASK >= (1 << 8))// 有不支持硬件扫描的通道需要扫描 
        M0P_ADC->CR1_f.HTCMP = 1;           // 使能高阈值比较（转换完成中断）
        M0P_ADC->HT = 0;                    // 高阈值为0，转换结果在[0..4095]之间都会产生高阈值中断（=转换完成中断）
        #if (ADC_SCAN_CHEN_MASK & (1 << 8))     // 通道8需要扫描
        M0P_ADC->CR0_f.SEL = 8;
        #elif (ADC_SCAN_CHEN_MASK & (1 << 9))   // 通道9需要扫描
        M0P_ADC->CR0_f.SEL = 9;
        #elif (ADC_SCAN_CHEN_MASK & (1 << 10))  // 通道10需要扫描
        M0P_ADC->CR0_f.SEL = 10;
        #elif (ADC_SCAN_CHEN_MASK & (1 << 11))  // 通道11需要扫描
        M0P_ADC->CR0_f.SEL = 11;
        #endif
        M0P_ADC->CR0_f.START = 1;           // 开始转换
    #else
        M0P_ADC->CR0_f.IE = 0;      // 关闭ADC中断
        mScanning = false;          // 扫描结束，标记不在扫描（单次转换将跳出等待）
        mIsBusy = false;            
    #endif
	}
#endif
    if(M0P_ADC->IFR_f.HHT_INTF) // 单次转换完毕
    {
#ifdef ADC_USE_SCAN
        if(mScanning)   // 正在扫描
        {
    #if (ADC_SCAN_CHEN_MASK & (1 << 8))     // 扫描列表里有通道8
            if(M0P_ADC->CR0_f.SEL == 8)
            {
                _Adc_ScanCallBackCh8(M0P_ADC->RESULT);
        #if (ADC_SCAN_CHEN_MASK & (1 << 9))   // 通道9需要扫描
                M0P_ADC->CR0_f.SEL = 9;
                M0P_ADC->CR0_f.START = 1;           // 开始转换
        #elif (ADC_SCAN_CHEN_MASK & (1 << 10))  // 通道10需要扫描
                M0P_ADC->CR0_f.SEL = 10;
                M0P_ADC->CR0_f.START = 1;           // 开始转换
        #elif (ADC_SCAN_CHEN_MASK & (1 << 11))  // 通道11需要扫描
                M0P_ADC->CR0_f.SEL = 11;
                M0P_ADC->CR0_f.START = 1;           // 开始转换
        #else                                   // 没有通道需要扫描
                M0P_ADC->CR0_f.IE = 0;      // 关闭ADC中断
                M0P_ADC->CR1_f.HTCMP = 0;   // 关闭高阈值比较
                mScanning = false;          // 扫描结束，标记不在扫描（单次转换将跳出等待）
                mIsBusy = false;    
        #endif
            }
    #endif
    #if (ADC_SCAN_CHEN_MASK & (1 << 9))   // 扫描列表里有通道9
            if(M0P_ADC->CR0_f.SEL == 9)
            {
                _Adc_ScanCallBackCh9(M0P_ADC->RESULT);
        #if (ADC_SCAN_CHEN_MASK & (1 << 10))  // 通道10需要扫描
                M0P_ADC->CR0_f.SEL = 10;
                M0P_ADC->CR0_f.START = 1;           // 开始转换
        #elif (ADC_SCAN_CHEN_MASK & (1 << 11))  // 通道11需要扫描
                M0P_ADC->CR0_f.SEL = 11;
                M0P_ADC->CR0_f.START = 1;           // 开始转换
        #else                                   // 没有通道需要扫描
                M0P_ADC->CR0_f.IE = 0;      // 关闭ADC中断
                M0P_ADC->CR1_f.HTCMP = 0;   // 关闭高阈值比较
                mScanning = false;          // 扫描结束，标记不在扫描（单次转换将跳出等待）
                mIsBusy = false;    
        #endif
            }
    #endif
    #if (ADC_SCAN_CHEN_MASK & (1 << 10))   // 扫描列表里有通道10
            if(M0P_ADC->CR0_f.SEL == 10)
            {
                _Adc_ScanCallBackCh10(M0P_ADC->RESULT);
        #if (ADC_SCAN_CHEN_MASK & (1 << 11))  // 通道11需要扫描
                M0P_ADC->CR0_f.SEL = 11;
                M0P_ADC->CR0_f.START = 1;           // 开始转换
        #else                                   // 没有通道需要扫描
                M0P_ADC->CR0_f.IE = 0;      // 关闭ADC中断
                M0P_ADC->CR1_f.HTCMP = 0;   // 关闭高阈值比较
                mScanning = false;          // 扫描结束，标记不在扫描（单次转换将跳出等待）
                mIsBusy = false;    
        #endif
            }
    #endif
    #if (ADC_SCAN_CHEN_MASK & (1 << 11))   // 扫描列表里有通道11
            if(M0P_ADC->CR0_f.SEL == 11)
            {
                _Adc_ScanCallBackCh11(M0P_ADC->RESULT);
                M0P_ADC->CR0_f.IE = 0;      // 关闭ADC中断
                M0P_ADC->CR1_f.HTCMP = 0;   // 关闭高阈值比较
                mScanning = false;          // 扫描结束，标记不在扫描（单次转换将跳出等待）
                mIsBusy = false;    
            }
    #endif
        }
    #ifdef ADC_USE_ASYNC
        else
    #endif
#endif
    #ifdef ADC_USE_ASYNC
        {
            M0P_ADC->CR0_f.IE = 0;      // 关闭ADC中断
            M0P_ADC->CR1_f.HTCMP = 0;   // 关闭高阈值比较
            mResultCall(M0P_ADC->RESULT);   // 通知结果
            mIsBusy = false;
        #if ((defined ADC_USE_SCAN) && (defined ADC_USE_TIMING_SCAN))
            Tim2_EnableInt();        // 允许中断中开启扫描转换(定时扫描可以继续)
        #endif
        }
    #endif
    }
#endif
	M0P_ADC->ICLR = 0;	// 清除所有的中断标志位
}

#if ((defined ADC_USE_SCAN) && (defined ADC_USE_TIMING_SCAN))
// TIM2每隔一段时间中断一次，启动一次ADC扫描转换
void TIM2_IRQHandler()
{
	Adc_StartScan();	// 启动一次ADC扫描转换
	M0P_TIM2->ICLR = 0;
}
#endif

void Adc_Init()
{
    //SYSCTRL_ENABLE_ADC_CLK;
#if (HC32_MCU_SERIES == HC32F005)
    M0P_ADC->CR0 = (ADC_CLKSEL << 2) | (ADC_SAM << 11) | (ADC_SREF << 8) | (ADC_BUFEN << 10);
    M0P_ADC->CR1 = 0;   // 注意复位值不为0
    M0P_ADC->CR2 =  (ADC_HW_SCAN_CNT << 8) | ((ADC_SCAN_CHEN_MASK & 0x00FF) << 0);  // 设置硬件扫描
    M0P_ADC->CR0_f.ADCEN = ADC_ENABLE;   
#else
    M0P_ADC->CR0 = (ADC_CLKSEL << 2) | (ADC_SAM << 12) | (ADC_SREF << 9) | (ADC_BUFEN << 11)
                    |(1 << 14)      // 使能内部参考电压 是否必要待测试
                    ;
    M0P_ADC->CR0_f.EN = ADC_ENABLE;
#endif
    M0P_BGR->CR = (ADC_EN_TS << 1)  // 启动内部温度传感器
                | (1 << 0);         // 使能BGR

#if (defined ADC_USE_SCAN || defined ADC_USE_ASYNC)
    NVIC_ClearPendingIRQ(ADC_IRQn);        // 清除NVIC中断标志位
    NVIC_SetPriority(ADC_IRQn, 3);         // 优先级 3 （0~3）
    NVIC_EnableIRQ(ADC_IRQn);              // NVIC允许中断
#endif
#if ((defined ADC_USE_SCAN) && (defined ADC_USE_TIMING_SCAN))
    Tim2_Init();
#endif
}

#if (defined ADC_USE_SCAN)
void Adc_StartScan()
{
#if (HC32_MCU_SERIES == HC32F005)   
    if(mScanning == false)    //检查是否正在扫描
    {
        while(mIsBusy);     // 等待异步转换结束，结束后已经是单次模式
        mScanning = true;                   // 标记正在扫描
        mIsBusy = true;                     // 标记ADC繁忙
    #if(ADC_HW_SCAN_CNT > 0)// 需要启动硬件扫描(通道0~7支持硬件扫描)
        M0P_ADC->CR1_f.CT = 1;              // 切换到连续（扫描）转换模式（仅支持通道0~7）
        M0P_ADC->CR0_f.STATERST = 1;        // 复位连续转换状态
        M0P_ADC->ICLR_f.CONT_INTC = 0;      // 清除连续转换完成标志位（中断标志位）
    #else
        M0P_ADC->CR1_f.HTCMP = 1;           // 使能高阈值比较（转换完成中断）
        M0P_ADC->HT = 0;                    // 高阈值为0，转换结果在[0..4095]之间都会产生高阈值中断（=转换完成中断）
        #if (ADC_SCAN_CHEN_MASK & (1 << 8))     // 通道8需要扫描
        M0P_ADC->CR0_f.SEL = 8;
        #elif (ADC_SCAN_CHEN_MASK & (1 << 9))   // 通道9需要扫描
        M0P_ADC->CR0_f.SEL = 9;
        #elif (ADC_SCAN_CHEN_MASK & (1 << 10))  // 通道10需要扫描
        M0P_ADC->CR0_f.SEL = 10;
        #elif (ADC_SCAN_CHEN_MASK & (1 << 11))  // 通道11需要扫描
        M0P_ADC->CR0_f.SEL = 11;
        #endif
    #endif
        M0P_ADC->CR0_f.IE = 1;              // 使能中断
        M0P_ADC->CR0_f.START = 1;           // 开始转换
    }
#endif
}
#endif

u16 Adc_GetOnce(u8 ch)
{
    u16 r;
#if (HC32_MCU_SERIES == HC32F005)   // 005没有单次转换结束中断
#if ((defined ADC_USE_SCAN) || (defined ADC_USE_ASYNC))
    while(mIsBusy);     // 等待异步转换结束，结束后是单次模式
    #if ((defined ADC_USE_SCAN) && (defined ADC_USE_TIMING_SCAN))
    Tim2_DisableInt();     // 禁止中断中开启扫描转换，必须保证单次转换过程中不会启动扫描转换
    #endif
#endif
    M0P_ADC->CR0_f.SEL = ch;
    M0P_ADC->CR0_f.START = 1;
    while(M0P_ADC->CR0_f.START !=0);
    r = M0P_ADC->RESULT;
#if ((defined ADC_USE_SCAN) && (defined ADC_USE_TIMING_SCAN))
    Tim2_EnableInt();        // 允许中断中开启扫描转换
#endif
#else
    // while(mIsBusy);     // 等待扫描转换结束，结束后已经是单次模式
    //ADC_DISALLOW_INT_START_SCAN;     // 禁止中断中开启扫描转换
    M0P_ADC->CR0_f.SGLMUX = ch;
    M0P_ADC->SGLSTART = 1;
    while(M0P_ADC->IFR_f.SGLIF==0);
    M0P_ADC->ICR_f.SGLIC = 0;   // 清零转换结束标志位（SGLIF）
    //ADC_DISALLOW_INT_START_SCAN;     // 禁止中断中开启扫描转换
    r = M0P_ADC->RESULT;
#endif
    return r;
}

u16 MedianFilter16b9(u16 *dat);
// [同步]获取一次有效的（中值滤波）AD值
// 注意：需要先把对应通道的IO口设置为模拟信号模式
u16 Adc_GetValue(u8 ch)
{
    u8 i;
	u16 ad[9], a;

	for (i = 0; i < 9; i++) //连续采集9次
	{
		ad[i] = Adc_GetOnce(ch);
	}
	a = MedianFilter16b9(ad); //中值滤波
	return a;
}

// [同步]获取VDD电压，单位mV
u16 Adc_GetVDDVoltage()
{
#if (ADC_SREF == 3) // 使用VDD作为参考电压
    u16 v12v = Adc_GetValue(_ADC_CH_V12);
    u16 bv = (u16)((u32)1200*ADC_FULL_VALUE/v12v);
#else
    u16 vddv = Adc_GetValue(_ADC_CH_VDD_3);
    u16 bv = (u32)vddv*(ADC_FULL_VOLTAGE*3)/ADC_FULL_VALUE;
#endif
    return bv;
}




#ifdef ADC_USE_ASYNC
void Adc_StartOnce(u8 ch, adc_result_callback_t result_callback)
{
    mResultCall = result_callback;
    while(mIsBusy);
#if ((defined ADC_USE_SCAN) && (defined ADC_USE_TIMING_SCAN))
    Tim2_DisableInt();        // 禁止中断中开启扫描转换，必须保证单次转换过程中不会启动扫描转换
#endif
    M0P_ADC->ICLR_f.HHT_INTC = 1;       // 清除高阈值中断标志位【可优化】
    M0P_ADC->CR1_f.HTCMP = 1;           // 使能高阈值比较（转换完成中断）
    M0P_ADC->HT = 0;                    // 高阈值为0，转换结果在[0..4095]之间都会产生高阈值中断（=转换完成中断）
    M0P_ADC->CR0_f.SEL = ch;
    M0P_ADC->CR0_f.IE = 1;              // 使能ADC中断
    M0P_ADC->CR0_f.START = 1;           // 开始转换
}
#endif
